/*
   Copyright 2007 Marco Slot (marco.slot@gmail.com)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package nl.marcoslot.lib.calc;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import nl.marcoslot.lib.gui.layout.alm.ComponentFieldReference;
import nl.marcoslot.lib.io.ParseException;
import nl.marcoslot.lib.io.ParserTools;

/**
 * Parser for mathematical expressions in infix notation.
 *
 * @author Marco Slot
 */
public class ExpressionParser {
    
    public static final int VALUE               = 1;
    public static final int OPERATOR            = 2;
    public static final int CLOSING_PARENTHESIS = 3;
    public static final int OPENING_PARENTHESIS = 4;
    
    private VariableController variableController;
    
    /**
     * Constructs a new ExpressionParser.
     */
    public ExpressionParser() {
        variableController = new VariableController() {
            public Value getVariable(String name) {
                return new Value() {
                    public double value() {
                        return 0;
                    }
                };
            }
        };
    }
    
    /**
     * Parses an expression from a CharacterIterator.
     *
     * @param exp
     *    the CharacterIterator
     * @throws nl.marcoslot.lib.io.ParseException
     *    if there is a parsing error
     * @return
     *    the expression
     */
    public Expression parse(CharacterIterator exp) throws ParseException {
        return parse(exp, new Expression());
    }
    
    /**
     * Parses an expression from a CharacterIterator.
     *
     * @param exp
     *    the CharacterIterator
     * @param result
     *    the Expression object to put the result in
     * @throws nl.marcoslot.lib.io.ParseException
     *    if there is a parsing error
     * @return
     *    the expression
     */
    public Expression parse(CharacterIterator exp, Expression result) throws ParseException {
        ShuntingYard yard;
        Object[] output;
        
        result.init();
        yard = new ShuntingYard();
        parseExpression(exp, yard);
        return yard.close();
    }
    
    private void parseExpression(CharacterIterator exp, ShuntingYard result) throws ParseException{
        int last = 0;
        
        ParserTools.skipWhiteSpace(exp);
        
        while(exp.current() != CharacterIterator.DONE && exp.current() != ';') {
            if(Character.isDigit(exp.current())) {
                if(last == VALUE)               throw new ParseException("Value follows value in expression .");
                if(last == CLOSING_PARENTHESIS) throw new ParseException("Value follows closing parenthesis in expression.");
                parseNumber(exp, result);
                last = VALUE;
            } else if(Character.isLetter(exp.current())) {
                if(last == VALUE)               throw new ParseException("Value follows value in expression.");
                if(last == CLOSING_PARENTHESIS) throw new ParseException("Value follows closing parenthesis in expression.");
                parseVariable(exp, result);
                last = VALUE;
            } else if(Operator.isOperator(exp.current())) {
                if(last == OPERATOR)            throw new ParseException("Operator follows operator in expression.");
                if(last == OPENING_PARENTHESIS) throw new ParseException("Operator follows opening parenthesis in expression.");
                parseOperator(exp, result);
                last = OPERATOR;
            } else if(exp.current() == '(') {
                if(last == VALUE)               throw new ParseException("Opening parenthesis follows operator in expression.");
                if(last == CLOSING_PARENTHESIS) throw new ParseException("Opening parenthesis follows closing parenthesis in expression.");
                ParserTools.readChar(exp);
                result.add(new OpeningParenthesis());
                last = OPENING_PARENTHESIS;
            } else if(exp.current() == ')') {
                if(last == OPERATOR)            throw new ParseException("Closing parenthesis follows operator in expression.");
                if(last == OPENING_PARENTHESIS) throw new ParseException("Closing parenthesis follows opening parenthesis in expression.");
                ParserTools.readChar(exp);
                result.add(new ClosingParenthesis());
                last = CLOSING_PARENTHESIS;
            } else {
                throw new ParseException("Illegal symbol in Expression.");
            }
            
            ParserTools.skipWhiteSpace(exp);
        }
    }
    
    private void parseOperator(CharacterIterator exp, ShuntingYard result) throws ParseException {
        try {
            result.add(new Operator(ParserTools.readChar(exp)));
        } catch(Operator.UnknownOperatorException e) {
            throw new ParseException("Unknown Operator.");
        }
    }
    
    private void parseVariable(CharacterIterator exp, ShuntingYard result) throws ParseException {
        StringBuffer name;
        
        name = new StringBuffer();
        
        while(Character.isLetterOrDigit(exp.current())) {
            name.append(ParserTools.readChar(exp));
        }
        
        if(ParserTools.readChar(exp) != '.')
            throw new ParseException("Missing '.' in reference.");
        else
            name.append('.');
        
        while(Character.isLetterOrDigit(exp.current())) {
            name.append(ParserTools.readChar(exp));
        }
        
        result.add(getNamedValue(name.toString()));
        
    }
    
    private void parseNumber(CharacterIterator exp, ShuntingYard result) throws ParseException {
        StringBuffer number;
        
        number = new StringBuffer();
        
        while(Character.isDigit(exp.current()) || exp.current() == '.') {
            number.append(ParserTools.readChar(exp));
        }
        
        try {
            result.add(new Number(Double.parseDouble(number.toString())));
        } catch(NumberFormatException e) {
            throw new ParseException("Invalid number.");
        }
    }
    
    private Value getNamedValue(String name) throws ParseException {
        return variableController.getVariable(name);
    }
    
    /**
     * Sets the VariableController for this parser. Identifiers in the expression
     * will be passed to the controller.
     *
     * @param controller
     *   the variable controller to use
     * @return
     *   this object
     */
    public ExpressionParser setVariableController(VariableController controller) {
        variableController = controller;
        return this;
    }
    
}
